package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Auditable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @author atguigu-mqx
 * @ClassName TsLoginAspect
 * @description: TODO
 * @date 2024年06月03日
 * @version: 1.0
 */
@Component
@Aspect
public class TsLoginAspect {

    //  @annotation(org.springframework.transaction.annotation.Transactional)
    //  利用spring.io 官方文档有提示!
    //  @Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 登录切面类
     *
     * @param joinPoint
     * @param tsLogin
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(tsLogin)")
    public Object tsLogin(ProceedingJoinPoint joinPoint, TsLogin tsLogin) throws Throwable {
        /*
        登录成功之后，login() success: 在请求头中存储一个token; 如果没有登录过，请求头中没有 token.
            还需要将用户信息存储到缓存！ 缓存redis 使用数据类型是什么? key value;
            login:
                hash: hset key field value; hget key field;  存储对象的时候，使用hash 数据结构.
                hset user:userId:info phone 1342328012;
                便于修改：hset user:userId:info phone 1342328012; 不需要动其他属性数据.
                string: set key value; get key; userInfo.toString();可以... 因为用户信息数据不会超过512m; 选hash;
                    String userJson = get user:userId:info;
                    UserInfo userInfo = JSON.parseObject(userJson,UserInfo.class);
                SSO: 单点登录！ 一处登录，处处使用！
                    将用户存储在缓存中！
                    key=? value=?
                    select * from user_info where userName = ? and pwd = ?;
                    userInfo != null
                        setRedis(userInfo); key = ? value=?;  key=userId; value=userInfo.toString();
                list:
                set:
                ZSet:
         */
        //  判断请求头中是否有token 存在
        //  从 spring 容器 request 上下文请求对象中获取
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //  httpServletRequest.getHeader("token");
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();

        try {
            //  判断token 是否为空
            String token = request.getHeader("token");
            //  根据tsLogin 判断是否需要登录
            if (tsLogin.required()) {
                //  需要登录
                //  判断token 是否为空
                if (StringUtils.isEmpty(token)) {
                    //  必须提示用户登录
                    throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
                }
                //  如果不为空，则判断 token 是否有效 如果token 有效，则放行
                //  利用token 获取缓存数据，判断缓存数据是否存在！
                String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
                //  强制转换为UserInfo对象
                UserInfo userInfo = (UserInfo) this.redisTemplate.opsForValue().get(loginKey);
                //  判断当前userInfo 对象是否为空
                if (null == userInfo){
                    //  必须提示用户登录 则提示用户登录
                    throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
                }
            }
            //  在有些场景下，这个可能不需要用户登录也能访问！
            if (!StringUtils.isEmpty(token)){
                //  获取用户信息
                String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
                //  强制转换为UserInfo对象
                UserInfo userInfo = (UserInfo) this.redisTemplate.opsForValue().get(loginKey);
                //  判断当前userInfo 对象是否为空
                if (null != userInfo){
                    //   缓存中有数据,为了后续其他微服务方便获取userId ，在此将用户Id 放入ThreadLocal
                    AuthContextHolder.setUserId(userInfo.getId());
                    //  AuthContextHolder.setUsername(userInfo.getNickname());
                }
            }
            //  要执行带有注解的方法体
            return joinPoint.proceed();
        } finally {
            //  是否内存空间，防止内存泄漏。
            AuthContextHolder.removeUserId();
        }

    }

}
